﻿
namespace EasyNet.Solr.Impl
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Text;

    using Commons;
    using Commons.Params;

    public class JsonUpdateParametersConvert : IUpdateParametersConvert<string>
    {
        public string ConvertUpdateParameters(UpdateOptions updateOptions)
        {
            var sb = new StringBuilder();

            sb.Append('{');

            var addComma = false;

            addComma = EncodeDocs(sb, updateOptions.Docs);
            addComma = EncodeCommitOptions(sb, updateOptions.CommitOptions, addComma);
            addComma = EncodeOptimizeOptions(sb, updateOptions.OptimizeOptions, addComma);
            addComma = EncodeDelById(sb, updateOptions.DelById, addComma);
            addComma = EncodeDelByQ(sb, updateOptions.DelByQ, addComma);

            sb.Append("}");

            return sb.ToString();
        }

        public string ConvertRollbackParameters()
        {
            return string.Format("{{\"{0}\":true}}", UpdateParams.ROLLBACK);
        }

        private bool EncodeDocs(StringBuilder sb, IEnumerable<SolrInputDocument> docs)
        {
            if (docs == null || docs.Count() == 0)
            {
                return false;
            }

            sb.Append("\"add\":[");

            var addComma = false;

            foreach (var doc in docs)
            {
                if (addComma) sb.Append(',');

                EncodeDoc(sb, doc);

                addComma = true;
            }

            sb.Append(']');

            return true;
        }

        private static void EncodeDoc(StringBuilder sb, SolrInputDocument doc)
        {
            if (doc == null)
            {
                return;
            }

            sb.Append('{');

            var addComma = false;

            if (doc.Boost != 1.0f)
            {
                sb.AppendFormat("\"boost\":{0}", doc.Boost);

                addComma = true;
            }

            foreach (var field in doc)
            {
                if (addComma)
                {
                    sb.Append(',');
                }

                EncodeSolrInputField(sb, field.Value);

                addComma = true;
            }

            sb.Append('}');
        }

        private static void EncodeSolrInputField(StringBuilder sb, SolrInputField field)
        {
            if (field == null)
            {
                return;
            }

            EncodeString(sb, field.Name);

            sb.Append(':');

            if (field.Boost == 1.0f)
            {
                EncodeAny(sb, field.Value);
            }
            else
            {
                sb.Append('{');
                sb.AppendFormat("\"boost\":{0}", field.Boost);
                sb.Append("\"value:\"");

                EncodeAny(sb, field.Value);

                sb.Append('}');
            }
        }

        private static bool EncodeCommitOptions(StringBuilder sb, CommitOptions? commitOptions, bool appendComma)
        {
            if (!commitOptions.HasValue)
            {
                return false;
            }

            if (appendComma)
            {
                sb.Append(',');
            }

            sb.AppendFormat("\"{0}\":", "commit");
            sb.Append('{');

            var addComma = false;

            if (commitOptions.Value.ExpungeDeletes.HasValue)
            {
                sb.AppendFormat("\"{0}\":", UpdateParams.EXPUNGE_DELETES);

                EncodeAny(sb, commitOptions.Value.ExpungeDeletes.Value);

                addComma = true;
            }
            if (commitOptions.Value.WaitFlush.HasValue)
            {
                if (addComma)
                {
                    sb.Append(',');
                }

                sb.AppendFormat("\"{0}\":", UpdateParams.WAIT_FLUSH);

                EncodeAny(sb, commitOptions.Value.WaitFlush.Value);

                addComma = true;
            }
            if (commitOptions.Value.WaitSearcher.HasValue)
            {
                if (addComma)
                {
                    sb.Append(',');
                }

                sb.AppendFormat("\"{0}\":", UpdateParams.WAIT_SEARCHER);

                EncodeAny(sb, commitOptions.Value.WaitSearcher.Value);
            }


            sb.Append('}');

            return true;
        }

        private static bool EncodeOptimizeOptions(StringBuilder sb, OptimizeOptions? optimizeOptions, bool appendComma)
        {
            if (!optimizeOptions.HasValue)
            {
                return false;
            }

            if (appendComma)
            {
                sb.Append(',');
            }

            sb.AppendFormat("\"{0}\":", UpdateParams.OPTIMIZE);
            sb.Append('{');

            var addComma = false;

            if (optimizeOptions.Value.MaxSegments.HasValue)
            {
                sb.AppendFormat("\"{0}\":", UpdateParams.MAX_OPTIMIZE_SEGMENTS);

                EncodeAny(sb, optimizeOptions.Value.MaxSegments.Value);

                addComma = true;
            }
            if (optimizeOptions.Value.WaitFlush.HasValue)
            {
                if (addComma)
                {
                    sb.Append(',');
                }

                sb.AppendFormat("\"{0}\":", UpdateParams.WAIT_FLUSH);

                EncodeAny(sb, optimizeOptions.Value.WaitFlush.Value);

                addComma = true;
            }
            if (optimizeOptions.Value.WaitSearcher.HasValue)
            {
                if (addComma)
                {
                    sb.Append(',');
                }

                sb.AppendFormat("\"{0}\":", UpdateParams.WAIT_SEARCHER);

                EncodeAny(sb, optimizeOptions.Value.WaitSearcher.Value);
            }


            sb.Append('}');

            return true;
        }

        public static bool EncodeDelById(StringBuilder sb, IEnumerable<string> delById, bool appendComma)
        {
            if (delById == null || delById.Count() == 0)
            {
                return false;
            }

            if (appendComma)
            {
                sb.Append(',');
            }

            var addComma = false;

            foreach (var id in delById)
            {
                if (addComma)
                {
                    sb.Append(',');
                }

                sb.Append("\"delete\":{\"id\":");
                EncodeString(sb, id);
                sb.Append('}');

                addComma = true;
            }

            return true;
        }

        public static bool EncodeDelByQ(StringBuilder sb, IEnumerable<string> delByQ, bool appendComma)
        {
            if (delByQ == null || delByQ.Count() == 0)
            {
                return false;
            }

            if (appendComma)
            {
                sb.Append(',');
            }

            var addComma = false;

            foreach (var id in delByQ)
            {
                if (addComma)
                {
                    sb.Append(',');
                }

                sb.Append("\"delete\":{\"query\":");
                EncodeString(sb, id);
                sb.Append('}');

                addComma = true;
            }

            return true;
        }

        private static void EncodeAny(StringBuilder sb, object o)
        {
            if (o == null)
            {
                sb.Append("\"null\"");
            }
            else if (o is string || o is Enum || o is Guid || o is char)
            {
                EncodeString(sb, o.ToString());
            }
            else if (o is sbyte || o is byte || o is short || o is ushort || o is int || o is uint || o is long || o is ulong || o is decimal || o is double || o is float)
            {
                sb.Append(Convert.ToString(o, NumberFormatInfo.InvariantInfo));
            }
            else if (o is DateTime)
            {
                sb.AppendFormat("\"{0}\"", ((DateTime)o).ConvertToString());
            }
            else if (o is bool)
            {
                sb.Append(o.ToString().ToLower());
            }
            else if (o is IEnumerable)
            {
                EncodeArray(sb, o as IEnumerable);
            }
            else
            {
                EncodeString(sb, o.ToString());
            }
        }

        private static void EncodeArray(StringBuilder sb, IEnumerable i)
        {
            sb.Append('[');

            var addComma = false;

            foreach (object o in i)
            {
                if (addComma)
                {
                    sb.Append(',');
                }

                EncodeAny(sb, o);

                addComma = true;
            }

            sb.Append(']');
        }

        private static void EncodeString(StringBuilder sb, string s)
        {
            sb.Append('"');

            foreach (char c in s)
            {
                switch (c)
                {
                    case '\t': sb.Append("\\t"); break;
                    case '\r': sb.Append("\\r"); break;
                    case '\n': sb.Append("\\n"); break;
                    case '"':
                    case '\\': sb.Append("\\" + c); break;
                    default: sb.Append(c >= ' ' && c < 128 ? c.ToString() : "\\u" + ((int)c).ToString("X4")); break;
                }
            }

            sb.Append('"');
        }
    }
}
